<?xml version="1.0"?>

<st-source>
<time-stamp>From VisualWorks® NonCommercial, Pre-Release 7.7 (feb09.3) of February 20, 2009 on February 27, 2009 at 02:40:01 am</time-stamp>
<!-- Package QIFImporter(1.2,rob)= -->


<component-property>
<name>QIFImporter</name> <type>package</type>
<property>developmentPrerequisites</property> <value>#(#(#any 'Sepher-Currency' ''))</value>
</component-property>

<component-property>
<name>QIFImporter</name> <type>package</type>
<property>prerequisiteParcels</property> <value>#(#('Sepher-Currency' ''))</value>
</component-property>

<name-space>
<name>QIF</name>
<environment>Sepher</environment>
<private>false</private>
<imports>
			private Smalltalk.*
			private Smalltalk.Sepher.*
			</imports>
<category>QIFImporter</category>
<attributes>
<package>QIFImporter</package>
</attributes>
</name-space>

<comment>
<name-space-id>Sepher.QIF</name-space-id>
<body></body>
</comment>

<class>
<name>QIFTransaction</name>
<environment>Sepher.QIF</environment>
<super>Core.Object</super>
<private>false</private>
<indexed-type>none</indexed-type>
<inst-vars>type date amount status num payee memo address category splits </inst-vars>
<class-inst-vars></class-inst-vars>
<imports></imports>
<category>Bookkeeper-Support</category>
<attributes>
<package>QIFImporter</package>
</attributes>
</class>

<comment>
<class-id>Sepher.QIF.QIFTransaction</class-id>
<body></body>
</comment>

<class>
<name>QIFBankTransaction</name>
<environment>Sepher.QIF</environment>
<super>Sepher.QIF.QIFTransaction</super>
<private>false</private>
<indexed-type>none</indexed-type>
<inst-vars></inst-vars>
<class-inst-vars></class-inst-vars>
<imports></imports>
<category>Bookkeeper-Support</category>
<attributes>
<package>QIFImporter</package>
</attributes>
</class>

<comment>
<class-id>Sepher.QIF.QIFBankTransaction</class-id>
<body></body>
</comment>

<class>
<name>QIFSplit</name>
<environment>Sepher.QIF</environment>
<super>Core.Object</super>
<private>false</private>
<indexed-type>none</indexed-type>
<inst-vars>transaction amount memo category </inst-vars>
<class-inst-vars></class-inst-vars>
<imports></imports>
<category>Bookkeeper-Support</category>
<attributes>
<package>QIFImporter</package>
</attributes>
</class>

<comment>
<class-id>Sepher.QIF.QIFSplit</class-id>
<body></body>
</comment>

<class>
<name>QIFImporter</name>
<environment>Sepher.QIF</environment>
<super>Core.Object</super>
<private>false</private>
<indexed-type>none</indexed-type>
<inst-vars>fileName importStream transactions transactionClass currency type localeForReading </inst-vars>
<class-inst-vars>dateFormatString </class-inst-vars>
<imports></imports>
<category>Bookkeeper-Support</category>
<attributes>
<package>QIFImporter</package>
</attributes>
</class>

<comment>
<class-id>Sepher.QIF.QIFImporter</class-id>
<body>QIFImporter is a helper application to process QIF files and access the information in them as objects. This can be used by applications that want to import the information in the files.Instance Variables:	currency	&lt;Sepher.Currency&gt;	description of currency	fileName	&lt;String&gt;	description of fileName	importStream	&lt;Stream&gt;	description of importStream	transactionClass	&lt;Behavior&gt;	description of transactionClass	transactions	&lt;OrderedCollection&gt;	description of transactions	type	&lt;Symbol&gt;	description of type</body>
</comment>

<shared-variable>
<name>DefaultCurrency</name>
<environment>Sepher.QIF.QIFImporter</environment>
<private>false</private>
<constant>false</constant>
<category>currency default</category>
<attributes>
<package>QIFImporter</package>
</attributes>
</shared-variable>



<methods>
<class-id>Sepher.QIF.QIFTransaction class</class-id> <category>instance creation</category>

<body package="QIFImporter" selector="new">new	^super new initialize</body>
</methods>


<methods>
<class-id>Sepher.QIF.QIFTransaction</class-id> <category>accessing</category>

<body package="QIFImporter" selector="num:">num: aValue	num := aValue</body>

<body package="QIFImporter" selector="num">num	^num</body>

<body package="QIFImporter" selector="address">address	^address</body>

<body package="QIFImporter" selector="memo">memo	^memo</body>

<body package="QIFImporter" selector="amount:">amount: aValue	amount := aValue</body>

<body package="QIFImporter" selector="splits">splits	^splits</body>

<body package="QIFImporter" selector="amount">amount	^amount</body>

<body package="QIFImporter" selector="payee">payee	^payee</body>

<body package="QIFImporter" selector="category">category	^category</body>

<body package="QIFImporter" selector="status:">status: aValue	status := aValue</body>

<body package="QIFImporter" selector="addSplit:">addSplit: aQIFSplit 	"Splits send this message - they are responsible for adding themselves to the transaction they belong to."	splits add: aQIFSplit</body>

<body package="QIFImporter" selector="status">status	^status</body>

<body package="QIFImporter" selector="date">date	^date</body>

<body package="QIFImporter" selector="memo:">memo: aValue	memo := aValue</body>

<body package="QIFImporter" selector="type">type	^type</body>

<body package="QIFImporter" selector="type:">type: aValue	type := aValue</body>

<body package="QIFImporter" selector="category:">category: aValue	category := aValue</body>

<body package="QIFImporter" selector="address:">address: aValue	address := aValue</body>

<body package="QIFImporter" selector="payee:">payee: aValue	payee := aValue</body>

<body package="QIFImporter" selector="date:">date: aValue	date := aValue</body>

<body package="QIFImporter" selector="transferCategory">transferCategory	"Answer the category string in case the category designates a transfer. Because the transfer category string can contain classifications we need to be careful."	^self category copyFrom: (self category indexOf: $[) + 1		to: (self category indexOf: $]) - 1</body>
</methods>

<methods>
<class-id>Sepher.QIF.QIFTransaction</class-id> <category>testing</category>

<body package="QIFImporter" selector="hasSplits">hasSplits	^self splits notEmpty</body>

<body package="QIFImporter" selector="isTransfer">isTransfer	"QIF notation for transfer is enclosed in [ ]. Take into account classifications."	self category isNil ifTrue: [^false].	^(self category includes: $[) and: [self category last == $]]</body>

<body package="QIFImporter" selector="isSplit">isSplit	^false</body>
</methods>

<methods>
<class-id>Sepher.QIF.QIFTransaction</class-id> <category>printing</category>

<body package="QIFImporter" selector="printOn:">printOn: aStream 	aStream		nextPutAll: type printString;		cr.	aStream		nextPutAll: date printString;		cr.	aStream nextPutAll: amount printString.	self hasSplits ifTrue: [aStream nextPutAll: ' (split)'].	aStream cr.	aStream		nextPutAll: (status isNil ifTrue: [''] ifFalse: [status]);		cr.	aStream		nextPutAll: (num isNil ifTrue: [''] ifFalse: [num]);		cr.	aStream		nextPutAll: (payee isNil ifTrue: [''] ifFalse: [payee]);		cr.	aStream		nextPutAll: (memo isNil ifTrue: [''] ifFalse: [memo]);		cr.	aStream nextPutAll: (category isNil ifTrue: [''] ifFalse: [category])</body>
</methods>

<methods>
<class-id>Sepher.QIF.QIFTransaction</class-id> <category>initialize-release</category>

<body package="QIFImporter" selector="initialize">initialize	splits := OrderedCollection new</body>
</methods>


<methods>
<class-id>Sepher.QIF.QIFSplit</class-id> <category>accessing</category>

<body package="QIFImporter" selector="transaction:">transaction: aValue 	transaction := aValue.	transaction addSplit: self</body>

<body package="QIFImporter" selector="category:">category: aValue	category := aValue</body>

<body package="QIFImporter" selector="transaction">transaction	^transaction</body>

<body package="QIFImporter" selector="memo">memo	^memo</body>

<body package="QIFImporter" selector="amount:">amount: aValue	amount := aValue</body>

<body package="QIFImporter" selector="transferCategory">transferCategory	"Answer the category string in case the category designates a transfer. Because the transfer category string can contain classifications we need to be careful."	^self category copyFrom: (self category indexOf: $[) + 1		to: (self category indexOf: $]) - 1</body>

<body package="QIFImporter" selector="payee">payee^transaction payee</body>

<body package="QIFImporter" selector="amount">amount	^amount</body>

<body package="QIFImporter" selector="memo:">memo: aValue	memo := aValue</body>

<body package="QIFImporter" selector="category">category	^category</body>
</methods>

<methods>
<class-id>Sepher.QIF.QIFSplit</class-id> <category>printing</category>

<body package="QIFImporter" selector="printOn:">printOn: aStream 	aStream nextPutAll: '(split)'.	aStream cr.	aStream		nextPutAll: (transaction payee isNil 					ifTrue: ['']					ifFalse: [transaction payee]);		cr.	amount isNil ifFalse: [amount printOn: aStream].	aStream cr.	aStream		nextPutAll: (memo isNil ifTrue: [''] ifFalse: [memo]);		cr.	aStream nextPutAll: (category isNil ifTrue: [''] ifFalse: [category])</body>
</methods>

<methods>
<class-id>Sepher.QIF.QIFSplit</class-id> <category>testing</category>

<body package="QIFImporter" selector="isSplit">isSplit	^true</body>

<body package="QIFImporter" selector="hasSplits">hasSplits	^false</body>

<body package="QIFImporter" selector="isTransfer">isTransfer	"QIF notation for transfer is enclosed in [ ]. Take into account classifications."	self category isNil ifTrue: [^false].	^(self category includes: $[) and: [self category last == $]]</body>
</methods>


<methods>
<class-id>Sepher.QIF.QIFImporter class</class-id> <category>class initialization</category>

<body package="QIFImporter" selector="initialize">initialize	"QIFImporter initialize"	DefaultCurrency := #{Sepher.CurrencyUnit.Euro} value</body>
</methods>

<methods>
<class-id>Sepher.QIF.QIFImporter class</class-id> <category>accessing</category>

<body package="QIFImporter" selector="dateFormatString:">dateFormatString: anObject	dateFormatString := anObject</body>

<body package="QIFImporter" selector="dateFormatString">dateFormatString	^dateFormatString</body>
</methods>

<methods>
<class-id>Sepher.QIF.QIFImporter class</class-id> <category>instance creation</category>

<body package="QIFImporter" selector="openOn:">openOn: aFilename 	"QIFImporter openOn: '\\triffid\users\rob\Projecten\Privé\Budget\upgrade\Rabo Betaal Gez.qif' asFilename"	^self new initialize filename: aFilename</body>

<body package="QIFImporter" selector="open">open	^self new initialize</body>
</methods>


<methods>
<class-id>Sepher.QIF.QIFImporter</class-id> <category>initialize-release</category>

<body package="QIFImporter" selector="filename:">filename: aFilename 	"On giving the name of the file to import, immediately do the import."	fileName := aFilename.	self readTransactions</body>

<body package="QIFImporter" selector="initialize">initialize	transactions := OrderedCollection new.	currency := DefaultCurrency.	localeForReading := Locale current</body>
</methods>

<methods>
<class-id>Sepher.QIF.QIFImporter</class-id> <category>accessing</category>

<body package="QIFImporter" selector="fileName">fileName	^fileName</body>

<body package="QIFImporter" selector="transactions">transactions	^transactions</body>

<body package="QIFImporter" selector="currency">currency	^currency</body>

<body package="QIFImporter" selector="localeForReading">localeForReading	^localeForReading</body>

<body package="QIFImporter" selector="localeForReading:">localeForReading: anObject	localeForReading := anObject</body>

<body package="QIFImporter" selector="currency:">currency: aCurrency	currency := aCurrency</body>
</methods>

<methods>
<class-id>Sepher.QIF.QIFImporter</class-id> <category>testing</category>

<body package="QIFImporter" selector="defaultLocaleForDate">defaultLocaleForDate	"Is the locale set for reading the same as the current locale?"	^Locale current == self localeForReading</body>

<body package="QIFImporter" selector="isValidQIF">isValidQIF	"Rather minimal test for valid QIF, that looks for a first line containing the String !Type:."	| stream answer |	answer := false.		[stream := fileName asFilename readStream.	((stream upTo: Character cr) startsWith: '!Type:') 		ifTrue: [answer := true]] 			ensure: [stream close].	^answer</body>

<body package="QIFImporter" selector="peekForTransfer:">peekForTransfer: aReadStream 	"Determine if the next element in the argument, aReadStream, is a transfer."	^aReadStream peek == $[</body>
</methods>

<methods>
<class-id>Sepher.QIF.QIFImporter</class-id> <category>private</category>

<body package="QIFImporter" selector="readTransactionsFrom:">readTransactionsFrom: aStream 	"Read the type of the account from the file and iterate over the transactions, converting each."	aStream reset.	type := ((aStream upTo: Character cr) tokensBasedOn: $:) last.	type = 'Bank' 		ifTrue: [transactionClass := #{Sepher.QIF.QIFBankTransaction} value]		ifFalse: [transactionClass := #{Sepher.QIF.QIFTransaction} value].	[aStream atEnd] whileFalse: [self readTransactionFrom: (aStream upTo: $^)]</body>

<body package="QIFImporter" selector="readTransactions">readTransactions	"Read the subsequent statements in the QIF file and create the transactions from it."	Cursor read showWhile: 			[			[importStream := fileName asFilename readStream.			self readTransactionsFrom: importStream] 					ensure: [importStream close]]</body>

<body package="QIFImporter" selector="readTransactionFrom:">readTransactionFrom: aString	"The argument, aString, contains one transaction in QIF format. Convert this into an intermediate transaction."	| read newTransaction peek split |	newTransaction := transactionClass new.	transactions add: newTransaction.	read := aString readStream.		[read skipSeparators.	peek := read peek.	peek isNil] whileFalse: 				[peek == $! ifTrue: [^self readTypeFrom: read].				peek == $D ifTrue: [self readDateInto: newTransaction from: read].				peek == $U ifTrue: [self readNoopInto: newTransaction from: read].				peek == $M ifTrue: [self readMemoInto: newTransaction from: read].				peek == $T ifTrue: [self readAmountInto: newTransaction from: read].				peek == $N ifTrue: [self readNumInto: newTransaction from: read].				peek == $C ifTrue: [self readStatusInto: newTransaction from: read].				peek == $L ifTrue: [self readCategoryInto: newTransaction from: read].				peek == $S 					ifTrue: 						[split := QIFSplit new transaction: newTransaction.						self startSplitInto: split from: read].				peek == $E ifTrue: [self readSplitMemoInto: split from: read].				peek == $$ ifTrue: [self readSplitAmountInto: split from: read].				peek == $P ifTrue: [self readPayeeInto: newTransaction from: read]]</body>

<body package="QIFImporter" selector="skipMemorizedIn:">skipMemorizedIn: aReadStream 	"We encountered a memorized transactions section. Skip this transaction and scan through the filestream to see if there is another section.."	importStream skipThrough: $!.	importStream atEnd ifFalse: [self halt]</body>
</methods>

<methods>
<class-id>Sepher.QIF.QIFImporter</class-id> <category>private-reading</category>

<body package="QIFImporter" selector="retryWithCLocale">retryWithCLocale	"We encountered an error that requires attempting to read the QIF file with the C Locale for dates."	self initialize.	self localeForReading: (Locale named: #C).	self readTransactionsFrom: importStream</body>

<body package="QIFImporter" selector="readSplitAmountInto:from:">readSplitAmountInto: split from: read 	read skip: 1.	split amount: (Sepher.Currency 				newAmount: ((Locale named: #C) readNumberFrom: read nextLine readStream						type: FixedPoint)				currencyUnit: self currency)</body>

<body package="QIFImporter" selector="readSplitTransferInto:from:">readSplitTransferInto: split from: read 	split category: read nextLine</body>

<body package="QIFImporter" selector="readTypeFrom:">readTypeFrom: aReadStream 	"Special section of a QIF file, containing memorized transactions. We do nothing with these sections for now."	| sectionType |	aReadStream skipThrough: $:.	sectionType := aReadStream nextLine.	sectionType = 'Memorized' 		ifTrue: [self skipMemorizedIn: aReadStream]		ifFalse: [self halt]</body>

<body package="QIFImporter" selector="readNoopInto:from:">readNoopInto: newTransaction from: read 	read nextLine</body>

<body package="QIFImporter" selector="extractMemoFrom:into:">extractMemoFrom: newTransaction into: aReadStream 	"Extract a memo field, new lines allowed, into the newTransaction. Substitute all tab characters with new line characters"	newTransaction 		memo: (aReadStream nextLine replaceAll: Character tab with: Character cr)</body>

<body package="QIFImporter" selector="readMemoInto:from:">readMemoInto: newTransaction from: read 	read skip: 1.	self extractMemoFrom: newTransaction into: read</body>

<body package="QIFImporter" selector="readSplitCategoryInto:from:">readSplitCategoryInto: split from: read 	split category: read nextLine</body>

<body package="QIFImporter" selector="readNumInto:from:">readNumInto: newTransaction from: read 	read skip: 1.	newTransaction num: read nextLine</body>

<body package="QIFImporter" selector="readPayeeInto:from:">readPayeeInto: newTransaction from: read 	read skip: 1.	newTransaction payee: read nextLine</body>

<body package="QIFImporter" selector="readStatusInto:from:">readStatusInto: newTransaction from: read 	read skip: 1.	newTransaction status: read nextLine</body>

<body package="QIFImporter" selector="readDateInto:from:">readDateInto: newTransaction from: read 	"Default reading is in the current locale. When encountering an error in the format of these dates, reset the stream, and read in the C locale. When this fails as well, for now we only fail and raise an error to be handled elsewhere."	| dateString date |	read skip: 1.	dateString := read nextLine.	self defaultLocaleForDate 		ifTrue: 			[[date := Locale current readDateFrom: dateString readStream] on: Error				do: [:ex | ^self retryWithCLocale]]		ifFalse: [date := Date readFrom: dateString readStream].	newTransaction date: date</body>

<body package="QIFImporter" selector="readSplitMemoInto:from:">readSplitMemoInto: split from: read 	read skip: 1.	split memo: read nextLine</body>

<body package="QIFImporter" selector="readAmountInto:from:">readAmountInto: newTransaction from: read 	"We notice that QIF exports have amounts in English format."	read skip: 1.	newTransaction amount: (Sepher.Currency 				newAmount: ((Locale named: #C) readNumberFrom: read nextLine readStream						type: FixedPoint)				currencyUnit: self currency)</body>

<body package="QIFImporter" selector="startSplitInto:from:">startSplitInto: split from: read 	read skip: 1.	(self peekForTransfer: read) 		ifTrue: [self readSplitTransferInto: split from: read]		ifFalse: [self readSplitCategoryInto: split from: read]</body>

<body package="QIFImporter" selector="readCategoryInto:from:">readCategoryInto: newTransaction from: read 	read skip: 1.	newTransaction category: read nextLine</body>
</methods>



<initialize>
<class-id>Sepher.QIF.QIFImporter</class-id>
</initialize>

</st-source>
